# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.

package bamp::CppApiGen;
use bamp::Bamp;

sub new($)
{
    my $api = $_[1];
    my $self = { api => $api };
    bless $self;

    return $self;
}

sub arg_type($)
{
    my ($self, $arg) = @_;
    my $api = ${$self}{api};
    return defined $arg ? ${${$arg}[2]}{"C++"} : "void";
}

sub meth_name($)
{
    my ($self, $method) = @_;
    my $api = ${$self}{api};
    my $name = ${$method}{name};
    $name =~ s/_([a-z])/uc($1)/ge;
    return $name;
}

sub args($)
{
    my ($self, $method) = @_;
    my $api = ${$self}{api};
    my $ret = $api->first_out($method);

    my @args = ();
    for my $arg (@{${$method}{args}})
    {
        next if defined $ret && $arg == $ret && ${$arg}[1];
        my $n = ${$arg}[0];
        my $d = ${$arg}[1];
        my $t = ${${$arg}[2]}{"C++"};
        my $ref = ${${$arg}[4]}{"C++"}{ref} ? 1 : 0;
        push @args, "$t $n"        if $d eq "in" && !$ref;
        push @args, "const $t& $n" if $d eq "in" && $ref;
        push @args, "$t& $n"       if $d eq "out" || $d eq "io";
    }

    return @args;
}

sub header
{
    return $bamp::Bamp::licence;
}

sub arg_to_c($$)
{
    my ($self, $arg, $src) = @_;
    my $ctype   = ${${$arg}[2]}{"C"};
    my $cpptype = ${${$arg}[2]}{"C++"};

    # Convert the source expression to its corresponding C type. Add
    # application-specific type conversion rules here as necessary.
    return "$src"         if $cpptype eq $ctype;
    return "$src.c_str()" if $cpptype eq "std::string";
    return "$src.cptr()"  if $cpptype =~ /bamp::\w+Ptr/;
    return "$src.cptr()"  if $cpptype eq "bamp::Value";
    return "($ctype)($src)";
}


sub arg_from_c($$)
{
    my ($self, $arg, $src) = @_;
    my $ctype   = ${${$arg}[2]}{"C"};
    my $cpptype = ${${$arg}[2]}{"C++"};

    # Convert the source expression to its corresponding C type. Add
    # application-specific type conversion rules here as necessary.
    return "$src"            if $cpptype eq $ctype;
    return "$cpptype($src)";
}


sub declare($)
{
    my ($self, $meth) = @_;
    my $api      = ${$self}{api};
    my $ret      = $api->first_out($meth);
    my $rettype  = $self->arg_type($ret);
    my $methname = $self->meth_name($meth);
    my @args     = $self->args($meth);
    my $const    = ${$meth}{const} ? " const" : "";

    return "$rettype $methname(".join(', ', @args).")$const";
}

sub call_c_args($)
{
    my ($self, $meth, $retprefix) = @_;
    my @arglist;

    $retprefix = "" unless defined $retprefix;

    for my $arg (@{${$meth}{args}}) {
        my $argname = "";
        my $dir = ${$arg}[1];

        if ($dir eq "out" || $dir eq "io") {
            $argname = "&".$retprefix.${$arg}[0];
        } else {
            $argname = $self->arg_to_c($arg, ${$arg}[0]);
        }
        push @arglist, $argname;
    }
    return @arglist;
}

sub call_cpp_args($)
{
    my ($self, $meth, $retprefix) = @_;
    my $api = ${$self}{api};
    my $ret = $api->first_out($meth);

    $retprefix = "" unless defined $retprefix;

    my @arglist;
    for my $arg (@{${$meth}{args}}) {

        my $argname = "";
        my $dir = ${$arg}[1];
        my $ref = ${${$arg}[4]}{"C"}{ref} ? 1 : 0;

        next if defined $ret && $arg == $ret;
        
        if ($dir eq "out" || $dir eq "io")
        {
            $argname = $retprefix.${$arg}[0];
        }
        else
        {
            $argname = ${$arg}[0];
            $argname = $self->arg_from_c($arg, $argname);
            $argname =~ s/\&\*//;
        }
        push @arglist, $argname;
    }
    return @arglist;
}

sub parents()
{
    my ($self) = @_;
    my $api   = ${$self}{api};
    my $class = ${$api}{class};
    my @parents = ();

    # If the class has no parents, don't bother
    return @parents unless exists ${$api}{classes}{$class}{parents};

    # Resolve the C++ name of each parent and
    for my $parent (@{${$api}{classes}{$class}{parents}})
    {
        my $cpptype = ${${$api}{types}{$parent}{langs}{"C++"}}[0];
        die "$parent: C++ type not defined" unless defined $cpptype;
        push @parents, "public $cpptype";
    }

    return @parents;
}

1;
